/*
 *
 *  * Unidata Platform
 *  * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *  *
 *  * Commercial License
 *  * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *  *
 *  * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 *  * For clarification or additional options, please contact: info@unidata-platform.com
 *  * -------
 *  * Disclaimer:
 *  * -------
 *  * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 *  * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 *  * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 *  * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 *
 */

package org.unidata.mdm.rest.v1.dq.core.service;

import java.util.Collections;
import java.util.Objects;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.dq.core.context.GetQualityModelContext;
import org.unidata.mdm.dq.core.context.UpsertQualityModelContext;
import org.unidata.mdm.dq.core.dto.GetAssignmentsModelResult;
import org.unidata.mdm.dq.core.dto.GetQualityModelResult;
import org.unidata.mdm.dq.core.dto.GetRulesModelResult;
import org.unidata.mdm.dq.core.dto.GetSetsModelResult;
import org.unidata.mdm.dq.core.type.model.source.assignment.NameSpaceAssignmentSource;
import org.unidata.mdm.dq.core.type.model.source.rule.MappingSetSource;
import org.unidata.mdm.dq.core.type.model.source.rule.QualityRuleSource;
import org.unidata.mdm.rest.system.ro.DetailedErrorResponseRO;
import org.unidata.mdm.rest.system.service.AbstractRestService;
import org.unidata.mdm.rest.v1.dq.core.converter.MappingSetsConverter;
import org.unidata.mdm.rest.v1.dq.core.converter.NameSpaceAssignmentConverter;
import org.unidata.mdm.rest.v1.dq.core.converter.QualityRuleConverter;
import org.unidata.mdm.rest.v1.dq.core.ro.AssignmentsResultRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetAssignmentRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetAssignmentsRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetMappingSetRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetMappingSetsRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetQualityRuleRO;
import org.unidata.mdm.rest.v1.dq.core.ro.GetQualityRulesRO;
import org.unidata.mdm.rest.v1.dq.core.ro.MappingSetResultRO;
import org.unidata.mdm.rest.v1.dq.core.ro.QualityRuleResultRO;
import org.unidata.mdm.rest.v1.dq.core.ro.UpsertAssignmentRO;
import org.unidata.mdm.rest.v1.dq.core.ro.UpsertMappingSetRO;
import org.unidata.mdm.rest.v1.dq.core.ro.UpsertQualityRuleRO;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

/**
 * @author Alexey Tsarapkin
 */
@Path(QualityRuleRestService.SERVICE_PATH)
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public class QualityRuleRestService extends AbstractRestService {
    /**
     * Service path.
     */
    public static final String SERVICE_PATH = "quality-rules";
    /**
     * Tag.
     */
    public static final String SERVICE_TAG = "quality-rules";
    /**
     * The MMS.
     */
    @Autowired
    private MetaModelService metaModelService;
    /**
     * QRC.
     */
    private final QualityRuleConverter qualityRuleConverter = new QualityRuleConverter();
    /**
     * RSC.
     */
    private final MappingSetsConverter mappingSetsConverter = new MappingSetsConverter();
    /**
     * NAC.
     */
    private final NameSpaceAssignmentConverter nameSpaceAssignmentConverter = new NameSpaceAssignmentConverter();
    /**
     * Gets all rules.
     * @return all rules
     */
    @GET
    @Path("/rules")
    @Operation(
            description = "Returns all defined rules.",
            method = HttpMethod.GET,
            tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetQualityRulesRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response rules(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
                .allQualityRules(true)
                .draftId(draftId)
                .build());

        return ok(new GetQualityRulesRO(Objects.isNull(get.getRules())
                ? Collections.emptyList()
                : qualityRuleConverter.to(get.getRules().getRules())));
    }
    /**
     * Gets rule by id.
     *
     * @param id
     *            the id
     * @return the by id
     * @throws Exception
     *             the exception
     */
    @GET
    @Path("/rules/{id}")
    @Operation(
        description = "Gets a specific quality rule by ID",
        method = HttpMethod.GET,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetQualityRuleRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response ruleGet(
            @Parameter(description = "The ID. Required.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
            .qualityRuleIds(Collections.singletonList(id))
            .draftId(draftId)
            .build());

        GetQualityRuleRO result = new GetQualityRuleRO();
        GetRulesModelResult gfr = get.getRules();
        if (CollectionUtils.isNotEmpty(gfr.getRules()) && gfr.getRules().size() == 1) {
            result.setRule(qualityRuleConverter.to(gfr.getRules().get(0)));
        }

        return ok(result);
    }
    /**
     * Creates a new quality rule definition.
     * @param upsert the payload
     * @return state
     */
    @POST
    @Path("/rules")
    @Operation(
        description = "Creates a new quality rule definition.",
        method = HttpMethod.POST,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertQualityRuleRO.class)),
                description = "Rule definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetQualityRuleRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response ruleCreate(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertQualityRuleRO upsert) {

        QualityRuleSource target = qualityRuleConverter.from(upsert.getRule());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .rulesUpdate(target)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return ruleGet(target.getName(), draftId);
    }
    /**
     * Updates an existing quality rule definition.
     * @param upsert the payload
     * @return state
     */
    @PUT
    @Path("/rules/{id}")
    @Operation(
        description = "Updates an existing quality rule definition.",
        method = HttpMethod.PUT,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertQualityRuleRO.class)),
                description = "Rule definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetQualityRuleRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response ruleUpdate(
            @Parameter(description = "Existing rule ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertQualityRuleRO upsert) {

        QualityRuleSource target = qualityRuleConverter.from(upsert.getRule());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .rulesUpdate(target)
                .rulesDelete(!StringUtils.equals(id, target.getName()) ? id : null)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return ruleGet(target.getName(), draftId);
    }
    /**
     * Deletes an existing quality rule defibition.
     *
     * @param id the rule id
     * @return state
     */
    @DELETE
    @Path("/rules/{id}")
    @Operation(
        description = "Deletes an existing quality rule defibition by ID.",
        method = HttpMethod.DELETE,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = QualityRuleResultRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response ruleDelete(
            @Parameter(description = "Existing rule ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        metaModelService.upsert(UpsertQualityModelContext.builder()
                .rulesDelete(id)
                .draftId(draftId)
                .build());

        return ok(new QualityRuleResultRO(true));
    }
    /**
     * Gets all mapping sets.
     * @return all mapping sets
     */
    @GET
    @Path("/sets")
    @Operation(
            description = "Returns all defined rule mapping sets.",
            method = HttpMethod.GET,
            tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetMappingSetsRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response sets(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
                .allMappingSets(true)
                .draftId(draftId)
                .build());

        return ok(new GetMappingSetsRO(Objects.isNull(get.getSets())
                ? Collections.emptyList()
                : mappingSetsConverter.to(get.getSets().getSets())));
    }
    /**
     * Gets mapping rule set by id.
     *
     * @param id
     *            the id
     * @return the by id
     * @throws Exception
     *             the exception
     */
    @GET
    @Path("/sets/{id}")
    @Operation(
        description = "Gets a specific rule mapping set by ID",
        method = HttpMethod.GET,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetMappingSetRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response setGet(
            @Parameter(description = "The mapping set ID. Required.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
            .mappingSetIds(Collections.singletonList(id))
            .draftId(draftId)
            .build());

        GetMappingSetRO result = new GetMappingSetRO();
        GetSetsModelResult gfr = get.getSets();
        if (CollectionUtils.isNotEmpty(gfr.getSets()) && gfr.getSets().size() == 1) {
            result.setSet(mappingSetsConverter.to(gfr.getSets().get(0)));
        }

        return ok(result);
    }
    /**
     * Creates a new quality rule mapping set definition.
     * @param upsert the payload
     * @return state
     */
    @POST
    @Path("/sets")
    @Operation(
        description = "Creates a new quality rule mapping set definition.",
        method = HttpMethod.POST,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertMappingSetRO.class)),
                description = "Rule mapping set definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetMappingSetRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response setCreate(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertMappingSetRO upsert) {

        MappingSetSource target = mappingSetsConverter.from(upsert.getSet());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .setsUpdate(target)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return setGet(target.getName(), draftId);
    }
    /**
     * Updates an existing quality rule mapping set definition.
     * @param upsert the payload
     * @return state
     */
    @PUT
    @Path("/sets/{id}")
    @Operation(
        description = "Updates an existing quality rule mapping sets definition.",
        method = HttpMethod.PUT,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertMappingSetRO.class)),
                description = "Rule mapping set definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetMappingSetRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response setUpdate(
            @Parameter(description = "Existing rule mapping set ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertMappingSetRO upsert) {

        MappingSetSource target = mappingSetsConverter.from(upsert.getSet());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .setsUpdate(target)
                .setsDelete(!StringUtils.equals(id, target.getName()) ? id : null)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return setGet(target.getName(), draftId);
    }
    /**
     * Deletes an existing quality rule mapping set defibition.
     *
     * @param id the rule id
     * @return state
     */
    @DELETE
    @Path("/sets/{id}")
    @Operation(
        description = "Deletes an existing quality rule mapping set definition by ID.",
        method = HttpMethod.DELETE,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = MappingSetResultRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response setDelete(
            @Parameter(description = "Existing rule ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        metaModelService.upsert(UpsertQualityModelContext.builder()
                .setsDelete(id)
                .draftId(draftId)
                .build());

        return ok(new MappingSetResultRO(true));
    }
    /**
     * Gets all namespace assignments.
     * @return all namespace assignments
     */
    @GET
    @Path("/assignments")
    @Operation(
            description = "Returns all defined namespace assignments.",
            method = HttpMethod.GET,
            tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetAssignmentsRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response assignments(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
                .allAssignments(true)
                .draftId(draftId)
                .build());

        return ok(new GetAssignmentsRO(Objects.isNull(get.getAssignments())
                ? Collections.emptyList()
                : nameSpaceAssignmentConverter.to(get.getAssignments().getAssignments())));
    }
    /**
     * Gets namespace assignment by namespace id.
     *
     * @param id the namespace id
     * @return assignment
     */
    @GET
    @Path("/assignments/{id}")
    @Operation(
        description = "Gets a specific namespace assignment by namespace ID",
        method = HttpMethod.GET,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetAssignmentRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response assignmentGet(
            @Parameter(description = "The mapping set ID. Required.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        GetQualityModelResult get = metaModelService.get(GetQualityModelContext.builder()
            .assignmentIds(Collections.singletonList(id))
            .draftId(draftId)
            .build());

        GetAssignmentRO result = new GetAssignmentRO();
        GetAssignmentsModelResult gfr = get.getAssignments();
        if (CollectionUtils.isNotEmpty(gfr.getAssignments()) && gfr.getAssignments().size() == 1) {
            result.setAssignment(nameSpaceAssignmentConverter.to(gfr.getAssignments().get(0)));
        }

        return ok(result);
    }
    /**
     * Creates a new quality rule mapping set definition.
     * @param upsert the payload
     * @return state
     */
    @POST
    @Path("/assignments")
    @Operation(
        description = "Creates a new namespace assignment definition.",
        method = HttpMethod.POST,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertAssignmentRO.class)),
                description = "Namespace assignment definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetAssignmentRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response assignmentCreate(
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertAssignmentRO upsert) {

        NameSpaceAssignmentSource target = nameSpaceAssignmentConverter.from(upsert.getAssignment());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .assignmentsUpdate(target)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return assignmentGet(target.getNameSpace(), draftId);
    }
    /**
     * Updates an existing namespace assignment definition.
     * @param upsert the payload
     * @return state
     */
    @PUT
    @Path("/assignments/{id}")
    @Operation(
        description = "Updates an existing namespace assignment definition.",
        method = HttpMethod.PUT,
        tags = SERVICE_TAG,
        requestBody = @RequestBody(
                content = @Content(schema = @Schema(implementation = UpsertAssignmentRO.class)),
                description = "Existing namespace assignment definition."),
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = GetAssignmentRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response assignmentUpdate(
            @Parameter(description = "Existing rule mapping set ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId,
            UpsertAssignmentRO upsert) {

        NameSpaceAssignmentSource target = nameSpaceAssignmentConverter.from(upsert.getAssignment());
        metaModelService.upsert(UpsertQualityModelContext.builder()
                .assignmentsUpdate(target)
                .assignmentsDelete(!StringUtils.equals(id, target.getNameSpace()) ? id : null)
                .draftId(draftId)
                .waitForFinish(true)
                .build());

        return assignmentGet(target.getNameSpace(), draftId);
    }
    /**
     * Deletes an existing namespace assignment defibition by namespace id.
     *
     * @param id the namespace id
     * @return state
     */
    @DELETE
    @Path("/assignments/{id}")
    @Operation(
        description = "Deletes an existing namespace assignment defibition by namespace ID.",
        method = HttpMethod.DELETE,
        tags = SERVICE_TAG,
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = AssignmentsResultRO.class)), responseCode = "200"),
            @ApiResponse(content = @Content(schema = @Schema(implementation = DetailedErrorResponseRO.class)), responseCode = "500")
        }
    )
    public Response assignmentDelete(
            @Parameter(description = "Existing rule ID.", in = ParameterIn.PATH) @PathParam("id") String id,
            @Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY) @QueryParam("draftId") @DefaultValue("0") Long draftId) {

        metaModelService.upsert(UpsertQualityModelContext.builder()
                .assignmentsDelete(id)
                .draftId(draftId)
                .build());

        return ok(new AssignmentsResultRO(true));
    }
}
